Playground 展示语法和实时执行真实数据的特性,为编写方法和库接口提供了很好的机会。
Playground 展示语法和实时执行真实数据的特性,为编写方法和库接口提供了很好的机会。
通过实时编译我们能了解语法、写出例子以及获得方法如何使用的说明,所有这些就如一个活的文档展示在眼前。
不难想象我们也可以在 playground 里添加断言,以及创建真正的单元测试。或者更进一步,创建出符合条件的测试
Core Animation 是通过分离 layer 的模型属性和你在屏幕上看到的界面 (显示层) 的方式来设计的,这就导致我们很难去创建一个可以在任何时候能交互的动画,因为在动画时,模型和界面已经不能匹配了
它们中的一些确实属于 View Controller,但更多的是所谓的“表示逻辑(presentation logic)”,以 MVVM 属术语来说,就是那些将 Model 数据转换为 View 可以呈现的东西的事情,例如将一个 NSDate 转换为一个格式化过的 NSString
它能减少 View Controller 的复杂性并使得表示逻辑更易于测试。通过一些例子,我们将看到它如何达到这些目标。
MVVM 增加你的应用的可测试性。
一旦 View Model 上的 Model 发生改变,那 View 的属性也需要更新。Model 的改变应该级联向下通过 View Model 进入 View
在 OS X 上,我们可以使用 Cocoa 绑定,但在 iOS 上我们并没有这样好的配置可用。
例应该只用来保存全局的状态,并且不能和任何作用域绑定。如果这些状态的作用域比一个完整的应用程序的生命周期要短,那么这个状态就不应该使用单例来管理。用一个单例来管理用户绑定的状态,是代码的坏味道,你应该认真的重新评估你的对象图的设计
两种情况下其实都是 layer 在起决定作用。当然了,附加到 view 上的 layer 和单独的 layer 在行为上还是稍有不同的。
属性改变时 layer 会向 view 请求一个动作,而一般情况下 view 将返回一个 NSNull ,只有当属性改变发生在动画 block 中时,view 才会返回实际的动作
第一,接收者(接收对象改变的通知的对象)需要知道发送者 (值会改变的对象);第二,接收者需要知道发送者的生命周期,因为它需要在发送者被销毁前注销观察者身份。如果这两个要去符合的话,这个消息传递机制可以一对多(多个观察者可以注册观察同一个对象的变化)
通知的独特之处在于,发送者和接收者不需要相互知道对方,
这就意味着这种消息传递是单向的,我们不能回复一个通知。
delegate 可以通过返回值的形式来给发送者作出回应
基于 target-action 传递机制的一个局限是,发送的消息不能携带自定义的信息。在 Mac 平台上 action 方法的第一个参数永远接收者。iOS 中,可以选择性的把发送者和触发 action 的事件作为参数。除此之外就没有别的控制 action 消息内容的方法
不可变的集合完全是线程安全的,
NSCache 是例外,但它真的算不上是集合类,因为它不是一个通用的容器。
NSArray 实现了 objectAtIndexedSubscript: ,因为我们可以使用类 C 的语法 array[0] 来代替原来的 [array objectAtIndex:0]
在数组的开头和结尾插入/删除元素通常是一个 O(1)操作,而随机的插入/删除通常是 O(N) 的。
所谓值对象,就是指那些能够被判等的,持有某些数值的对象 (对它们判等时我们看重值是否相等,而对是否是同一个对象并不是那么关心)。通常来说,值对象会被用作 model 对象。
如果我们的属性不是 copy 而是 strong 的话,随着可变字符串的改变,我们的 Person 对象也将发生改变,这不是我们希望发生的
[ ⊙ω⊙ 主人,这是一个空标注耶! ]
好的 hash 函数需要兼备确定性和均布性。确定性需要保证对于同样的输入总是能生成同样的 hash 值。均布性需要保证输出的结果要在输出范围内均匀地对应输入。
使用可变对象的两个问题。其中一个是它们有可能在你不希望的时候发生改变,另一个是在多线程中使用可变对象。
GPU 是一个专门为图形高并发计算而量身定做的处理单元
布局过程
frame 是一个派生属性,是由 center 和 bounds 合成而来。不
尽量避免 drawRect: ,使用现有的视图构建自定义视图。
加了 target / action 模式。看
认的模式是将内容缩放以填充视图的范围,这在当视图的 frame 改变时并不会重新绘制。
建自定义控件时所面对的一个普遍的设计问题是向拥有它们的类中回传返回值。
必须检查代理是否实现了你想要调用的方法 (使用 respondsToSelector: ),
本地化过程中很有用的工具是 Auto Layout。
在 iOS 7 中的程序将会有望设置全局字体大小
对约束条件系统做出的任何改变都将自动触发这个方法。
通过调用 setNeedsUpdateConstraints 来触发这个操作,同时,你对约束条件系统做出的任何改变都将自动触发这个方法。
控制布局变化的所有权,
自定义视图中显示过程的所有权。
alignment rect 实际上是一个强大的新概念:从一个视图的视觉外观解耦出视图的 layout alignment edges。
最极端的情况是不调用父类的实现,自己重写全部的 layoutSubviews / layout 。这就意味着你在这个视图里的视图树里抛弃了自动布局。
一个没有格式的 UIView 既没有首选宽度也没有首选高度。
在你的实现中增加任何你需要布局子视图的约束条件之后,调用一下 [super updateConstraints]
为约束条件一旦创建后,只有其常量可以被改变。
使用这种方法,你可以对约束条件做出的改变并不局限于约束条件的常量。你可以删除约束条件,增加约束条件,甚至使用临时动画约束条件
通过约束条件将转换应用到视图布局上并不是一个好主意。
想使用 transform 来产生视图动画或者直接使它的 frame 动态化,最干净利索的技术是将这个视图嵌入到一个视图容器内,然后你可以在容器内重写 layoutSubviews,要么选择完全脱离自动布局
可以配置每个 session 的缓存,协议,cookie,以及证书策略(credential policy),甚至跨程序共享这些信息。
允许程序和网络基础框架之间相互独立,不会发生干扰。
另一大块就是 session task。它负责处理数据的加载以及文件和数据在客户端与服务端之间的上传和下载。
task 是由一个 NSURLSession 创建的。每个 task 的构造方法都对应有或者没有 completionHandler 这个 block 的两个版本
应用程序挂起,退出或者崩溃的情况下运行上传和下载任务。初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程(daemon)提供上下文。
在这个方法中,你不会被允许禁用何约束条件,
如果你仍然想使用约束条件布局子视图,你需要调用 [super layoutSubviews] / [super layout] ,然后对布局进行微调
将约束条件系统的解决方案应用到视图上
可以在第一次布局操作完成后再决定改变约束条件。比如,如果视图变得太窄的话,将原来排成一行的子视图转变成两行。
先,我们让自动布局做它的工作,然后用布局操作结果的 frame 更新给首选最大宽度,并且再次触发布局。
在这种情况下,我们不需要先调用 [super layoutSubviews] ,因为当 layoutSubviews 被调用时,label 就已经有一个 frame 了
约束条件自身动态化;
及改变约束条件重新计算 frame,并使用 Core Animation 将 frame 插入到新旧位置之间。
,你不需要手动设置视图的目标 frames,取而代之的是修改约束条件并触发一个布局操作为你设置 frames。在 iOS 中,代替
Core Animation 和 Auto Layout 结合在
。
首先,当你在不可满足的约束条件错误信息中看到 NSLayoutResizingMaskConstraints 时,你肯定忘了为你某一个视图设定 translatesAutoResizingMaskIntoConstraints 为 NO
(lldb) po 0x7731880
(lldb) po [0x7731880 superview]
(lldb) po [[0x7731880 superview] recursiveDescription]
。最简单的方法是使用调试控制台。你可以打印视图本身或它父视图的描述,甚至递归描述的树视图。这通常会提示你需要处理哪个视图。
(lldb) expr ((UIView *)0x7731880).backgroundColor = [UIColor purpleColor]
可以在屏幕上标注出来。比如,你可以改变它的背景颜色:
确保重新执行你的程序,否则改变不会在屏幕上显示出来。还要注意将内存地址转换为 (UIView *) ,以及额外的圆括号,这样我们就可以使用点操作。另外,你当然也可以通过发送消息来实
(lldb) expr [(UIView *)0x7731880 setBackgroundColor:[UIColor purpleColor]]
用 Instrument 的 allocation 模板,根据图表分析。一旦你从错误消息中得到内存地址(运行 Instruments 时,你从 Console 应用中获得的错误消息),你可以将 Instrument 的详细视图切换到 Objects List 页面,并且用 Cmd-F 搜索那个内存地址。这将会为你显示分配视图对象的方法,这通常是一个很好的暗示(至少对那些由代码创建的视图来说是这样的)。
在一个 category 中重写 NSLayoutConstraint 的描述,并且将视图的 tags 包含进去
constraintsWithVisualFormat:options:metrics:views: 方法有一个很有用的 option 参数。如果你还没有用过,请参见文档。这不同于格式化字符串只能影响一个视图,它允许你调整在一定范围内的视图。举个例子,如果用可视格式语言指定水平布局,那么你可以使用 NSLayoutFormatAlignAllTop 排列可视语言里所有视图为上边缘对齐。
NSLayoutFormatAlignAllCenterX 选项在父视图和子视图间创建了居中约束。
NSDictionaryFromVariableBindings 宏指令
NSDictionaryFromVariableBindings 宏指令,你传递一个可变数量的变量过去,返回得到一个键为变量名的字典
虽然 View 和 View Controller 是技术上不同的组件,但它们几乎总是手牵手在一起,成对的。你什么时候看到一个 View 能够与不同 View Controller 配对?或者反过来?所以,为什么不正规化它们的连接呢?
MVVM 是一个伟大的典范,它自身独立,只是在有一个良好的绑定框架时做得更好。
隐藏依赖是不好的。
这个例子和单例又有什么关系呢?用 Miško Hevery 的话来说,"单例就是披着羊皮的全局状态"
Xcode 甚至有一个默认的 "Dispatch Once" 代码片段,可以使我们非常简单地在代码中添加一个单例:
+ (instancetype)sharedInstance { static dispatch_once_t once; static id sharedInstance; dispatch_once(&once, ^{ sharedInstance = [[self alloc] init]; }); return sharedInstance; }
大多数的开发者都认同使用全局可变的状态是不好的行为。太多状态使得程序难以理解,难以调试。我们这些面向对象的程序员在最小化代码的状态复杂程度的方面,有很多需要向函数式编程学习的地方。
程序的任意模块都可以调用 [SPMySingleton sharedInstance] 并且访问这个单例。这意味着任何和这个单例交互产生的副作用都会影响程序其他地方的任意代码。
这里使用了单例,由于其具有全局和多状态的特性,导致隐式地在两个看起来完全不相关的模块之间建立了耦合
首先,他写了一个测试用例来保证网页查看器在设备没有连接时能够展示出错误信息。然后他写了一个测试用例来保证网页查看器能够正确的处理服务器错误。最后,他为成功情况时写了一个测试用例,来保证返回的网络内容能够被正确的显示出来。这个开发者运行了所有的测试用例,并且它们都如预期一样正确。赞! 几个月以后,这些测试用例开
HTTP是运行在应用层上的应用协议
注应用层 (application layer)、传输层 (transport layer) 和网络层 (internet layer),它们分别代表了典型的 HTTP 的应用的 HTTP,TCP 以及 IP。
探讨一种特殊的混合模式:基于 IP 的 TCP,以及基于 TCP 实现的 HTTP。这就是我们每天使用的 app 的基本网络配置。
IPv6 的表示形式为:八组十六进制数以冒号分割,比如:2001:0db8:85a3:0042:1000:8a2e:0370:7334
IPv4 地址是长度为 32 位。常见采用 dotted-decimal(点分十进制)表示法,具体形式如:198.51.100.42。
IP 网络中的主机都配有自己的地址,被称为 IP 地址。每个数据包中都包含了源主机和目标主机的 IP 地址。IP 协议负责路径计算,
IP 网络负责源主机与目标主机之间的数据包传输。IP 协议的特点是 best effort(尽力服务,其目标是提供有效服务并尽力传输)。这意味着,在传输过程中,数据包可能会丢失,也有可能被重复传送导致目标主机收到多个同样的数据包。
payload 中的内容即是要传输的真正信息,而 header 承载的是与传输数据有关的元数据 (metadata)。
TCP 是基于 IP 层的协议。但是 TCP 是可靠的、有序的、有错误检查机制的基于字节流传输的协议。
TCP 会确保所传输的数据的正确性
HTTP 是典型的 TCP 应用。用户浏览器(应用 1)与 web 服务器(应用 2)建立连接后,浏览器可以通过连接发送服务请求,web 服务器可以通过同样的连接对请求做出响应。
TCP 用不同的端口来区分应用。
凭借这样一对 IP 地址和端口号,就可以唯一标识一个连接。
HTTPS 的 web 服务器会监听 443 端口。浏览器作为发送源会启用一个临时端口结合自己的 IP 地址与目标服务器对应的端口和 IP 地址建立 TCP 连接
IPv4 的 Protocol 或 IPv6 的 Next Hearder的协议号被设置成 6,表示执行 TCP 协议。
主机之间传输的数据流一般先会被分块,再转化成 TCP 的报文段,最终会生成 IP 数据包中的 payload 载荷数据
TCP 报文段的 header 信息中主要包含的是源和目标端口号
第一个报文段的序列号是随机的,比如:1721092979,其后的每一个报文段的序列号都以此号为基础依次加 1
确认号,是目标端反馈给源的确认信息,通知源目前已经接到哪些报文段了
TCP 建立的是双向连接,通信双方可以同时进行数据的传输。
连接管
TCP的连接建立、数据传输以及连接终止的详细过程。
TCP 连接都是建立在两个主机之间的。所以,每个连接建立过程中都存在两个角色:
每个 TCP 报文段都有一个序列号。
在正式传输开始之前,源和目标端需要同步确认第一个报文的序列号。
10.0.1.6.52181 > 23.63.125.15.80 ,这一对是源和目标端的 IP 地址+端口
Flags 表示 TCP 报文段 header 信息中的一些缩写标识: S 代表 SYN, . 代表ACK, P 代表PUSH, F 是 FIN。
TCP 将流量控制和其他一系列复杂机制结合起来进行拥塞控制。需要处理以下问题:针对丢失的报文采用重发机制,同时还需要动态的调整发送报文的频率
A 发送数据包的过程不需要等待 B 的确认。
接收窗口 (receive window) 会告知发送方自身接收窗口数据缓冲区的大小。
关闭缓存
Apple 服务器响应的状态码是 200,这是标准的表示 HTTP 请求成功的状态码。
即便网络状况不好,只要连接还在,TCP 都会保证将请求发出去并且会一直等待响应的返回,只是时间长短的问题
一直以来都有一种误解,用重发请求来解决上面的问题。注意,这不是问题的关键,因为 TCP 有自己的重发机制。
正确的处理方式应该是:每当发起一个请求的时候,同时启动一个 10 秒计时器。如果请求在 10 秒之内返回,就把计时器停掉。如果超过 10 秒,可以给用户一个提示“网络不好,请稍后。”,我建议再给用户一个取消按钮,让他们可以自行选择等待还是取消请求,当然提示信息的具体内容和是否配备取消按钮,这个可以视乎各 app 的情况去决定。总而言之,开发者最好不要直接替用户做决定,比如直接中止他们的请求。
只要连接双方的 IP 地址是不变的、可用的,连接就一定会是“活跃”的。如果把 iPhone 从 Wi-Fi 连接切换到 3G 网络,这样连接就会变得不可用,因为手机的 IP 地址发生了变化,基于原 IP 地址创建的路由自然是失效的。
查 header 中缓存过期的相关属性,也可以直接利用 NSURLSession 中的 NSURLRequestUseProtocolCachePolicy 策略。
针对已请求的资源,只要服务器上对应的资源具备在一定时间内不发生变化特性,建议在本地缓存起来。
在一个请求被发送到服务器之前,系统会先查询共享的缓存信息
个 NSURLSession 对象都由一个 NSURLSessionConfiguration 对象来进行初始化,
负责数据的加载,
所有的 task 共享其创造者 NSURLSession 这一公共委托者(
所有的 task 都是可以取消,暂停或者恢复的
允许我们进一步的配置,
session 的 delegate 方法
task 的 delegate
configuration 只在初始化时被读取一次,之后都是不会变化的。
后台获取
API 就是远程通知
允许你在进程之外可以执行网络传输(下载和上传)工作。
来处理长时间运行的网络请求队列
何更新屏幕快照
在后台完成工作后更新程序快照,以用来呈现新的内容。
从 transition context 中得到了需要做转场的两个 view controller,然后使用最简单的 UIView animation 来实现了转场动画
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
completeTransition: 这个方法来更新 view controller 的状态
UIPercentDrivenInteractionTransition 类的一个实例,开发者不需要任何配置就可工
动画效果设置为交互式的(通过设置 interactionController 这个属性来实现
[interactionController updateInteractiveTransition:d];
该方法会根据用户手指拖动的距离计算一个百分比,切换的动画效果也随着这个百分比来走
系统都以一种解耦的方式自动地替我们完成了。
切换完成或者取消的时候,记得把 interaction controller 设置为 nil。因为如果下一次的转场是非交互的, 我们不应该返回这个旧的 interaction controller
用 GPUImage 定制动画
每渲染一帧就更新一下滤镜来实现动态的滤镜效果。使用 CADisplayLink 可以完成这个
实现交互式的转场效果,那么在这里,就不能使用时间,而是要根据手势来更新动画进
其在一个 view controller 中维护各种状态,不如再创建一个新的 view controller,使用自定义的转场动画,然后在这个转场动画中来移动你的各种 view。
建滤镜链(filter chain)也非常的直观,我们可以直接在样例代码的 setup 方法中看到如何构造它
这部分 API 大量的使用了协议而不是具体的对象
据不同的 operation(Push 或 Pop)返回不同的 animator。
让动画运行起来,我们创建一个自定义类,并且实现 UIViewControllerAnimatedTransitioning 这个协
无法控制你的代码在什么地方以及什么时候被调度,
无法控制执行多长时间后将被暂停,以便轮换执行别的任务
接使用线程可能会引发的一个问题是
如果你的代码和所基于的框架代码都创建自己的线程时,那么活动的线程数量有可能以指数级增长。这在大型工程中是一个常见问
两个基于队列的并发编程 API :GCD 和 operation queue
它们通过集中管理一个被大家协同使用的线程池,来解决上面遇到的问题
GCD 不仅决定着你的代码块将在哪个线程被执行,它还根据可用的系统资源对这些线程进行管理
GCD 在后端管理着一个线程池。GCD 不仅决定着你的代码块将在哪个线程被执行,它还根据可用的系统资源对这些线程进行管理
GCD 带来的另一个重要改变是,作为开发者可以将工作考虑为一个队列,而不是一堆线程,这种并行的抽象模型更容易掌握和使用
GCD 公开有 5 个不同的队列:运行在主线程中的 main queue,3 个不同优先级的后台队列,以及一个优先级更低的后台队列(用于 I/O)
5 个不同的队列
创建自定义队列:串行或者并行队列
低优先级的任务阻塞了高优先级任务
在绝大多数情况下使用默认的优先级队
主队列和自定义队列
操作队列(operation queue)是由 GCD 提供的一个队列模型的 Cocoa 抽
定义自己的 operations
对其进行排序
操作依赖是
操作队列是并发编程的首选工
重写 main
start 方法 来定义自己的 operations
状态属性(例如 isExecuting 和 isFinished )
管理操作的状
操作队列能够捕获到操作的改变
取消功能,
NSOperation 的子类
operation 添加到队列
block 添加到操作队
重写 operation 的 description 方
以有多少个操作参与并发执行
它并不能并行执行任务
配合任务的执行
定到某个特定的线程
责处理 UI 事件、计时器,以及其它内核相关事件
暂时性的将某个任务优先执行
在别的线程中添加一个 run loop
为优先级反转
多线程中访问共享资源。
多问题的根
必须被从内存中读出,然后增加计数器的值,最后还需要将这个增加后的值写回内存中
问题被叫做竞态条件
互斥的机制来访问共享资
互斥访问
优化策略引起的副作用
内存屏障
atomic 的形式来声明,就能支持互斥锁了
隐式的加锁和解锁操
线程有时候需要等
锁的竞争
多个线程在相互等待着对方的结束时,就会发生死锁,这时程序可能会被卡住
尽量减少线程间资源共
锁定的共享资源会引起读写问题
GCD 实现一个多读取单写入的模式
任务之间共享资源时,就可能发生优先级反转
不要使用不同的优先级
建议采纳的安全模式
GCD 是基于 C 的底层的 API
操作队列则是 GCD 实现的 Objective-C API
运行 block 时访问到已被释放的对象,在 block 中我们又需要将其转回 strong 引用。
个示例应用
理数据块的过程是先查看当前已缓冲的数据,并将新加入的数据附加上去。接下来它将按照换行符分解成小的部分,并处理每一行。
数据处理过程
相同的并发编程API
高层的 API 不仅可以完成底层 API 能完成的任务,而且能够让并发模型变得简单。
通过集中的管理线程,来缓解大量线程被创建的问题
run loop 可以运行在不同的模式中,
自行为其他线程设置 run loop ,
主线程一般来说都已经配置好了 main run loop
在别的线程中添加一个 run loop ,那么不要忘记在 run loop 中至少添加一个 input source 。
面的 block 只会运行一次。并且在连续的调用中,这种检查是很高效的。你能使用它来初始化全局数据比如单例。要注意的是,使用 dispatch_once_t 会使得测试变得非常困难(单例和测试不是很好配合)。
不能(直接)取消我
它允许你取消定时器的触发
动画产生作用的地方。
两个平行 layer 层次结构
性能怪兽
GPU 在合成方面非常高效
挑战1
GPU 需要将每一个 frame 的纹理(位图)合成在一起(一秒60次)。
挑战2
将数据从 RAM 移动到 VRAM 上
没有方法能告诉 GPU 纹理上的像素是透明还是不透明
Instruments 中 color blended layers 选项中所涉及的。
模拟器中的Debug菜单中也可用).
一个图片没有 alpha 通道和一个图片每个地方的 alpha 都是100%,这将会产生很大的不同
计算出屏幕上一个像素是什么颜色的时候,
第一个便是滚动
纹理的起点不在一个像素的边界上
Core Animation 工具和模拟器有一个叫做 color misaligned images 的选项
mask 是一个拥有 alpha 值的位
强制这种操作
强制离屏渲染缓存那些图层,
自动触发
序强制触发
合并/渲染图层树的一部分到
使用离屏渲染时,GPU 第一次会混合所有图层到一个
GPU 便可以复用这个位图缓存,
图层改变了,GPU 需要重新创建位图缓存
shouldRasterize 为 YES 来触发这个行为。
计算 GPU 的利用率和帧的速率来判断这个位图是否有用。
通常情况下 mask 只能被直接渲染到帧的缓冲区中(在屏幕内)。
Instrument 的 Core Animation 工具有一个叫做 Color Offscreen-Rendered Yellow 的选项,
一般情况下,你需要避免离屏渲染,因为这是很大的消耗。
因为这其中涉及两次昂贵的环境转换(转换环境到屏幕外缓冲区,然后转换环境到帧缓冲区)。
rasterized layer 的空间是有限的。苹
使用 layer 的方式会通过屏幕外渲染,你最好摆脱这种方式。为
Core Animation 允许你做非常高效的渲染
最繁重的任务便是判断出哪些图层需要被(重新)绘制
OpenGL ES 需要做的便是将图层合并、显示到屏幕上。
Core Animation 会请求分配一个纹理
Core Animation 会创建一个 OpenGL 纹理
许多低等级的 OpenGL ES 行为被简单易懂地封装到 CALayer 概念中。
Core Animation 处在渲染过程中的重要位置上
资源限制(即 CPU 和 GPU 的硬件资源)。
GPU 的渲染性能要比 CPU 高效很多,同时对系统的负载和消耗也更低一些
找出是哪一个限制你绘图性能的
使用 OpenGL ES Driver instrument。点击上面那个小的 i 按钮,
一个低级并且基于 C 的 API。
很多特色。
Quartz 2D 涉及到 2D 绘制的时候,它是非常强大的。有基于路径的绘制,反锯齿渲染,透明图层,分辨率,并且设备独立
UIKit 和 AppKit 都包含了 Quartz 2D 的一些简单 API
股票应用作为讲解 Quartz 2D 在代码中实现动态渲染的一个例子。
CPU 部分实现的绘制是通过 Quartz 2D 实现的。
使用 UIKit 或者 AppKit 时,上下文是唯一的。
使用 UIKit 或者 AppKit 时,上下文是唯一的。UIkit 维护着一个上下文堆栈,UIKit 方法总是绘制到最顶层的上下文中。你可以使用 UIGraphicsGetCurrentContext() 来得到最顶层的上下文。
上下文是唯一的
总是绘制到最顶层的上下文中
上下文堆栈,
在 UIKit 的堆栈中推进或取出上下文。
位图上下文。
屏幕上的像素是由红,绿,蓝三种颜色组件构成的。因
位图数据有时也被叫做 RGB 数据。
从每个像素中得到四个单独的值。
透明度会首先被乘以到像素值上
最终红、绿、蓝的值都会被预先乘以 alpha 的值
alpha 值被烘烤到红、绿、蓝的组件中
这个格式经常被叫做 ARGB。
另一个常见的格式便是 32bpp,
这常被叫做 xRGB
跳过第一个 alpha 值,
每一个独立的像素都对齐到 32-bit 的边界
这种格式很流行
灰度模式和 CMYK 格式
不包含 alpha 值。这
浮点数组件(有或没有 alpha 值)。
颜色组件(红、绿、蓝、alpha)
二维组件。
JPEG 可以非常非常好的压缩图片。
PNG 支持压缩带或不带 alpha 通道的颜色像素(RGB)
它的压缩对格式是无损的
图像解压缩算法是黑客最喜欢的攻击目标。
为原图设置 resizable images。你的文件将变得更小,因
这改变了像素的布局。
图层都有一个叫像素位图的后备存储,有点像一个图像
为图层设置了一个标识,
当你使用 UIKit 的绘制方法,
代替你的 -drawRect: 方
你并不需要实现 -drawRect: 方法。有时,通过 UIGraphicsBeginImageContextWithOptions() 或者 CGBitmapContextCeate() 创建位图会显得更有意义,
使用可变尺寸的图像来降低绘图系统的压力。
改变图像,
可以通过 UIGraphicsGetImageFromCurrentImageContext() ,将获得的这个上下文位图数据作为一个 UIImage,最终移除这个上下文
所有绘图的代码都是线程安全的
创建一个单独的渲染类,
在主队列中调用 view.image = image.这是一个非常重要的细节
为了将像素显示到屏幕上,一些处理将在 CPU 上进行。然后数据将会传送到 GPU,这也需要做一些相应的操作,最终像素显示到屏幕上。
对于屏幕上的每一个像素,GPU 需要算出怎么混合这些纹理来得到像素 RGB 的值。这就是合成大概的意思。
离屏渲染可以被 Core Animation 自动触发
合并/渲染图层树的一部分到一个新的缓冲区,然后该缓冲区被渲染到屏幕上。
绘制命令被推迟,并且在后台线程中异步执行
种方式就是先记录绘图命令,然后在后台线程中重现。
光栅化简单的说就是产生一组绘图指令并且生成一张图片
将一整张,切分若干块再逐个绘制成在一起
视图层级(view hierarchy)对于组合如何进行扮演了很重要的角色:
视图层级最顶端是窗口(window),
概念上,依次在每个视图上放置独立分层的图片并最终产生一个图片,单调的图像更容易被理解,特别是如果你以前使用过像 Photoshop 这样的工具
视图只关心一件事就是绘制它自己的 content。
雪球效应。
放置并设置视图的大小
大小通常是一样的
origin 经常是不同的
光栅化图片
这些图片便被一个接一个的绘制,并产生一个屏幕大小的图片,这便是上文所说的组合。
在组合的步骤中
在组合的步骤中,每个视图将自己光栅化图片组合到自己父视图的光栅化图片上面。
因为视图的 frame 和 bounds 矩形的大小总是一样的,所以光栅化图片组合的时候是像素对齐的。
一旦这两个视图被组合到一起,组合的结果图片将会和父视图的父视图进行组合,这是一个雪球效应。
视图图片的左上角会根据它 frame 的 origin 进行偏移
技术上讲,因为 iOS 处理组合方法的原因,你可以将一个子视图渲染在其父视图的 bounds 之外,但是光栅化期间的绘制不可能超出一个视图的 bounds
考虑一种我们可以实现的滚动
frame 不断改变的视图。
拖动的同时我增大视图的 origin.x
拖动时 frame 不断改变的视图。
时刻改变每个视图的 frames
组合一个 view 的光栅化图片到它父视图什么地方
通过改变这个紫色视图的 bounds,它每一个单独的子视图都被移动了。事实上,这正是 scroll view 工作的原理。
设置它的 contentOffset 属性时它改变 scroll view.bounds 的 origin。
content size 并不会改变其 bounds 的任何东西
content size 并不会改变其 bounds 的任何东西,所以这并不会影响 scroll view 如何组合自己的子视图
content size 定义了可滚动区域
content offset 的最小值;
content offset 的最大值是 content size 和 scroll view size 的差
滚动出可滚动区域。它的类型为 UIEdgeInsets,包含四个值:{top,left,bottom,right}。
除非没办法,否则你需要避免改变scroll view 的 content size
完全灵活的布局结构。
滚动经过 table view 的第一个或最后一个 cell 的边界时,table view将 content offset 弹回并复位
table view 将会允许用户通过 refresh control 中途停止滚动,并且将 refresh control 的顶部弹回到视图的顶部。
。当刷新动作被初始化时,content inset 已经被校正过,所以 content offset 的最小值包含了完整的 refresh control。
你可以认为 scroll view 的 bounds 为可滚动区域上的一个窗口
一个滚动的窗口,可以理解为放大镜工具,
contentInset 属性可以改变 content offset 的最大和最小值,
contentInset 属性可以改变 content offset 的最大和最小值,这样便可以滚动出可滚动区域。它的类型为 UIEdgeInsets,包含四个值:{top,left,bottom,right}。
调整扩展可滚动区域,即可以在可滚动区域之外滚动
用Content Insets对窗口稍作调整
调整offer 最小和最大值,即在可滚动区域之外滚动
调整offer 最小和最大值,即在不改变可滚动区域大小,就能在其范围之外滚动
在自己的代码中使用 content inset
允许在 content offset 的最大值下显示滚动区域外的区域。
UICollectionView 在此之上进行了进一步抽象。它将其子视图的位置,大小和外观的控制权委托给一个单独的布局对象。
过提供一个自定义布局对象,你几乎可以实现任何你能想象到的布局
布局对象 (Layout Objects)
类的功能:它通过一个接一个的放置 cell 来建立自己的布局,当需要的时候,插入横排或竖排的分栏符。通过自定义滚动方向,大小和 cell 之间的间距,flow layout 也可以在单行或单列中布局 cell
视图层级(view hierarchy
Supplementary views 相当于 table view 的 section header 和 footer views
数量和放置的位置完全由布局控制
Decoration views 纯粹为一个装饰品。他们完全属于布局对象,并被布局对象管理,
Supplementary views 和 decoration views 必须是 UICollectionReusableView 的子
能通过调用 registerClass: 或者 registerNib: 方法手动注册视图类了。你需要在 viewDidLoad 中做这些操作
两种类型的 collection view 布局:
1.独立于内容的布局计算。这正
基于内容的布局计算
如果有一个依赖内容的布局,那就是暗示你需要写自定义的布局类了
个 cell 的位置和外观不是基于其显示的内容,但所有 cell 的显示顺序是基于内容的顺序。
首先要提供的信息就是滚动区域大
首先要提供的信息就是滚动区域大小
布局对象必须在此时计算它内容的总大小
collection view 调用这个方法并传递一个自身坐标系统中的矩形过去
collection view 调用这个方法并传递一个自身坐标系统中的矩形过去。这个矩形代表了这个视图的可见矩形区域(也就是它的 bounds ),你需要准备好处理传给你的任何矩形
实现必须返回一个包含 UICollectionViewLayoutAttributes 对象的数
。 UICollectionViewLayoutAttributes 类包含了 collection view 内 item 的所有相关布局属性。默认情况下,这个类包含 frame , center , size , transform3D , alpha , zIndex 和 hidden 属性。如果你的布局想要控制其他视图的属性(比如背景颜色),你可以建一个 UICollectionViewLayoutAttributes 的子类,然后加上你自己的属性。
布局属性对象 (layout attributes objects) 通
items 从布局对象中请求到布局属性后,它将会实例化所有视图,并将对应的属性应用到每个视图上去
在原型设计和开发布局阶段,这是一个有效的方法。但是,这将对性能产生非常坏的影响,特别是可见 cell 远少于所有 cell 数量的时候,collection view 和布局对象将会为那些不可见的视图做额外不必要的工作。
需要你从 collection view 的数据源中取出你需要显示的数据。然后在循环中调用你实现的 layoutAttributesForItemAtIndexPath: 方法为每个 index path 创建并配置一个合适的布局属性对象,并将每个对象添加到数组
记住 supplementary 和 decoration views 的数量和种类完全由布局控制。
layoutAttributesForDecorationViewOfKind:atIndexPath: ,并且将这些对象加到数组中
在循环中调用你实现的 layoutAttributesForDecorationViewOfKind:atIndexPath: ,并且将这些对象加到数组中
这就是当其他三个方法开始起作用时,你实现的 layoutAttributesForItemAtIndexPath: 需要创建并返回一个单独的布局属性对象,这样才能正确的格式化传给你的 index path 所对应的 cell
如果你正在使用自动布局,你可能会感到惊讶,我们正在直接修改布局参数的 frame 属性,而不是和约束共事,但这正是 UICollectionViewLayout 的工作
collection view 将它的新 bounds 传给 shouldInvalidateLayoutForBoundsChange: 方法。这样我们便能比较视图当前的bounds 和新的 bounds 来确定返回值
一个优雅的方法:当一个 cell (或者supplementary或者decoration view)被插入到 collection view 中时,collection view 不仅向其布局请求 cell 正常状态下的布局属性,同时还请求其初始的布局属
。通过重写 UIResponder 的方法,可以决定一个类是否可以成为第一响应者 (first responder),例如当前输入焦点元素
它们被发送给第一响应者 (通常是一个视图)。如果第一响应者没有处理,则该行为沿着响应链到达视图控制器,如果行为仍然没有被处理,则继续传递给应用。
向视图添加交互的方法是使用手势识别。注意它们对 responders 并不起作用,而只对视图及其子类奏效。
UIControl 建立在视图上,增加了更多的交互支持。
这么做的好处是在自定义视图子类中需要做的事情很少,并且自动获得多目标支持
每个动作通常只有一个 block。另一个重要的限制是不要形成引用循环。如果你的视图控制器持有饼状图的强引用,饼状图持有 block,block 又持有视图控制器,就形成了一个引用循环。只要在 block 中引用 self 就会造成这个错误。所以通常代码会写成这个样子
抽离成独立的方法,这种情况的话可能用代理会更好一些。
在 drawRect: 方法被调用前,会为视图创建一个空白的图片来绘制 content。这个图片的坐标系统是视图的 bounds
为了实现这个平移功能,
巧妙的是通过改变这个紫色视图的 bounds,它每一个单独的子视图都被移动了
scrollvoew实现原理,即改变子视图的bounds 达到滚动功能.增加,漂移量,可滚动区域,以及调整偏移量的饭方法,来辅助scroller 控件
介绍了可滚动区域周围的填充。
其子视图的位置,大小和外观的控制权委托给一个单独的布局对象。通过提供一个自定义布局对象,你几乎可以实现任何你能想象到的布局
写一个 UICollectionViewLayout 的子类之前,你需要问你自己,你是否能够使用 UICollectionViewFlowLayout 实现你心里的布
Collection view 的 cell 必须是 UICollectionViewCell 的子类。
为了适应任意布局,collection view 建立了一个类似、但比 table view 更灵活的视图层级(view hierarchy
他们的内容都由数据源对象驱动
布局对象指定需要一个 decoration view 的时候,collection view 会自动创建,并将布局对象提供的布局参数应用到上面去。并不需要为自定义视图准备任何内容。
布局使用的每个视图类都需要在 collection view 中注册,这
在很多情况下,布局对象不仅需要取出当前可见 cell 的数据,还需要从所有记录中取出一些决定当前哪些 cell 可见的数据。
,布局对象如果访问某一个矩形内 cells 的属性,那就必须迭代数据源提供的所有事件来决定哪些位于要求的时间窗口中
,我们可以在一个独立(分页)的 scroll view (可以使用 UIPageViewController)中使用多个collection view(一周一个),或者坚持使用一个 collection view 并且返回足够大的内容宽度,这会使得用户感觉在两个方向上滑动自由。
UICollectionViewLayout的文档列出了子类需要重写的方法
们对应的 cell,supplementary view 或者 decoration view 关联在一
布局属性对象 (layout attributes objects) 通过 indexPath 属性和他们对应的 cell,supplementary view 或者 decoration view 关联在一
UICollectionViewLayoutAttributes 类包含了 collection view 内 item 的所有相关布局属性
所有 items 从布局对象中请求到布局属性后,它将会实例化所有视图,并将对应的属性应用到每个视图上去。
view 为所有 items 从布局对象中请求到布局属性
一个幼稚的实现可能会选择忽略传入的矩形,并且为 collection view 中的所有视图返回布局属
创建一个空的可变数组
为每个 index path 创建并配置一个合适的布局属性对象,并将每个对象添加到数组中
通过为 kind 参数传递你选择的不同字符,你可以区分出不同种类的supplementary views(比如headers和footers)。当需要创建视图时,collection view 会将 kind 字符传回到你的数据源
collection view 会为某个特殊的 cell,supplementary 或者 decoration view 向布局对象请求布局属性,而非所有可见的对
当 collection view 的 bounds 改变时,布局需要告诉 collection view 是否需要重新计算布局
自定义 collection view 布局也是向轻量级 view controller 迈出很好的一步
动画是叙述你的应用的故事的绝佳方式,在了解动画背后的基本原理之后,设计它们会轻松很
,动画帮助我们解释用户从哪里来,要到哪里去。
当把动画添加到一个 layer 时,是不直接修改它的属性
model layer tree(模型层树) 和 presentation layer tree(表示层树)。前者中的 layers 反映了我们能直接看到的 layers 的状态,而后者的 layers 则是动画正在表现的值的近
通过使用 -[CALayer presentationLayer] 和 -[CALayer modelLayer] ,你可以在两个 layer 之间轻松切换
Core Animation 维护了两个平行 layer 层次结构:
model layer tree(模型层树) 和 presentation layer tree(表示层树)。
动画的键路径
动画不会在超出其持续时间后还修改 presentation layer。
在结束时它甚至会被彻底移除。
一旦动画被移除,presentation layer 将回到 model layer 的值,
通过设置动画的 fillMode 属性为 kCAFillModeForward 以留在最终状态,并设置 removedOnCompletion 为 NO 以防止它被自动移
通过设置动画的 fillMode 属性为 kCAFillModeForward 以留在最终状态,并设置 removedOnCompletion 为 NO 以防止它被自动移除
实际上我们创建的动画对象在被添加到 layer 时立刻就复制了一份。这个特性在多个 view 中重用动画时这非常有用。
使用 CABasicAnimation 的 byValue 属性创建一个动画,这个动画从 presentation layer 的当前值开始,加上 byValue 的值后结束。
fromValue , byValue 和 toValue 的不同组合可以用来实现不同的效果
使用更通用的 CAKeyframeAnimation
关键帧(keyframe)使我们能够定义动画中任意的一个点,然后让 Core Animation 填充所谓的中间帧
设置 additive 属性为 YES 使 Core Animation 在更新 presentation layer 之前将动画的值添加到 model layer 中去。
为了填充所有路径,我们需要确定我们的火箭在任意时刻所到达的位置。
线性插值法来完成:
第一种方法是直接在 model layer 上更新属性
设置 keyTimes 属性让我们能够指定关键帧动画发生的时间。
使用 CGPathCreateWithEllipseInRect() ,我们创建一个圆形的 CGPath 作为我们的关键帧动画的 path
通过将其设置为 kCAAnimationPaced ,让 Core Animation 向被驱动的对象施加一个恒定速度
设置为 kCAAnimationPaced 将无视所有我们已经设置的 keyTimes
设置 rotationMode 属性为 kCAAnimationRotateAuto 确保飞船沿着路径旋
参数因子来进行插值。
把要进行动画的属性的插值从动画的速度中解耦出来
easing 函数)来实现这个目标。该函数通过修改持续时间的分数来控制动画的速度。
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
在 Core Animation 中,这个功能由 CAMediaTimingFunction 类表示
在一定限度内,你也可以使用 +functionWithControlPoints:::: 创建自己的 easing 函数。通过传递 cubic Bézier 曲线的两个控制点的 x 和 y 坐标,你可以轻松的创建自定义 easing 函数,比
CAAnimationGroup 来动画其中一个封面的代码
CAAnimationGroup *group = [[CAAnimationGroup alloc] init]; group.animations = @[ zPosition, rotation, position ]; group.duration = 1.2; group.beginTime = 0.5; [card.layer addAnimation:group forKey:@"shuffle"]; card.layer.zPosition = 1;
使用 CAAnimationGroup 得到的一个好处是可以将所有动画作为一个对象暴露出去。如果你要在应用程序中的多个地方用工厂对象创建的重用的动画的话,这将会非常有用。
默认情况下, CALayer 及其子类的绝大部分标准属性都可以执行动画,无论是添加一个 CAAnimation 到 Layer(显式动画),亦或是为属性指定一个动作然后修改它(隐式动画)
希望能同时为好几个属性添加动
不能通过使用标准 Layer 属性动画来实
动画不能通过使用标准 Layer 属性动画来实
何子类化 CALayer 并添加我们自己的属性,以便比较容易地创建那些如果以其他方式实现起来会很麻烦的动画效
添加到 CALayer 的子类上的可动画属性有三种类型:
能间接修改其它标准 Layer 属性的自定义属性是这些选项中最简单的。它
为我们修改这些属性后,它们就会继承任何被配置在当前 CATransaction 上的动画设置,并且自动执行动画
仅仅只是自定义 setter 方法
我们并没有创建一个新的可动画属性,而只是在单个方法里设置了几个标准可动画 Layer 属性而已
CALayer 具有为任何被声明的属性生成 dynamic 的 setter 和 getter 的能力
将属性标记为 @dynamic
如果我们想创建的动画并不能映射到任何已有的 Layer 属性上时,该
无论何时 time 属性改变时都能自动通知 CALayer 的方式,这样它才好重绘它的内容。我们通过覆写 +needsDisplayForKey: 方法即可做到这一点,
为 time 属性指定一个动画(或“动作(action)”),而通过覆写 -actionForKey: 方法就能做到:
而通过覆写 -display 方法,我们可以使用这些属性去控制任何我们想控制的东西,甚至是音量值这样的东西。
使用 Core Animation 的属性插值来平滑的在两个不同的音量之间渐变,以同样的方式我们可以动画 Layer 上的任何自定义属性:
通过把音量绑定到 dynamic 的 Layer 属性上
CALayer 不知道如何对 NSDate 属性进行插值(interpolate)(
可以保留我们的自定义 setter 方法并用它设置另一个等价于 NSTimeInterval 的动态属性(这是一个数字值,可以被插值)
你设置某个 CALayer 的某个属性,你实际设置的是 model Layer 的值 —— 这里的 model Layer 表示正在进行的动画结束时, Layer 所达到的最终状态。如果你取 model Layer 的值,它就总是给你它被设置到的最终值。
Layer 的 presentationLayer 的 time 属性,那我们就会看到我们所期望的插值。(
因为我们正在动画的是 time 值本身而不仅仅是时针或分针的位置,所以上下文信息被保留了。
用一个数组存储一些预先绘制好的图像,然后基于合适的插值简单的选择对应的图像即可。实
改变一个单独的 layer 的任何属性的时候,都会触发一个从旧的值过渡到新值的简单动画(这就是所谓的可动画 animatable )
当 layer 附加在 view 上时,它的默认的隐式动画的 layer 行为就不起作用了。
像 paths 这样的属性也是 animatable 的,但是它不支持隐式动画。
UIView 默认情况下禁止了 layer 动画,但
UIView 默认情况下禁止了 layer 动画,但是在 animation block 中又重新启用了它们
无论何时一个可动画的 layer 属性改变时,layer 都会寻找并运行合适的 'action' 来实行这个改变。
寻找动作,整个过程分为五个步骤
部分是最有意思的:
layer 通过向它的 delegate 发送 actionForLayer:forKey: 消息来询问提供一个对应属性变化的 action。
当 layer 在背后支持一个 view 的时候,view 就是它的 delegate;
可以看到在 block 外 view 返回的是 NSNull 对象,而在 block 中时返回的是一个 CABasicAnimation。
属性在动画 block 中改变时,view 将向 layer 返回一个基本的动画
控制板有两个状态:打开和关闭。
要将这个控制面板的所有状态都做到可以交互,甚至是在动画的过程中也可以,
UIKit 力学中的的动画是被间接驱动的
添加指定的行为到动画对象上来实现 UIDynamicItem 协议就能实现很多动画
能在任何时候被中断并且拥有连续的加速度
现我们的控制板的行为,我们将使用 UIkit 力学中的两个不同行为:UIAttachmentBehavior 和 UIDynamicItemBehavior
实现我们的控制板的行为,我们将使用 UIkit 力学中的两个不同行为:UIAttachmentBehavior 和 UIDynamicItemBehavior
控制板有三个不同状态:在开始或结束位置的静止状态,正在被用户拖动的状态,以及在没有用户控制时运动到结束位置的动画状态
使用的环境中没有 UIKit 力学 (比如说在 Mac 上),或者你的使用场景中不能很好的适用 UIKit 力学呢
为了填充所有路径,我们需要确定我们的火箭在任意时刻所到达的位置。这通常使用线性插值法来完成:
也就是说,对于动画给定的一个分数 t ,火箭的 x 坐标就是起始点的 x 坐标 77 ,加上一个到终点的距离 ∆x = 378 乘以该分数的值
有两个不同的基本策略:约束条件自身动态化;以及改变约束条件重新计算 frame,并使用 Core Animation 将 frame 插入到新旧位置之间。
直接使用约束条件动态化只是在 OS X 上的一种可行策略,
直接使用约束条件动态化只是在 OS X 上的一种可行策略,并且这对你能使用的动画有局限性
时不适合移动平台。
当使用 Core Animation 方法时,即使不使用自动布局,动画的工作方式在概念上也是一样的
,使用这种方法,你可以对约束条件做出的改变并不局限于约束条件的常量
由于新的约束只被解释一次来决定新的 frames,所以更复杂的布局改变都是有可能的。
Core Animation 和 Auto Layout 结合在一起产生视图动画时,自己不要接触视图的 frame。
session 的 delegate 方法处理连接层的问题,
NSURLSession 实际上给 Foundation 框架引入了一种全新的模式。这种模式允许 delegate 方法可以安全地在主线程与运行,而不会阻塞主线程;
task 的 delegate 则处理以网络请求为基础的问题
iOS 7 添加了两个新的 API 以便你的程序可以在后台更新界面以及内容。
后台获取 (Background Fetch) 和远程通知 (Remote Notification) 基于简单的 ApplicationDelegate 钩子,在应用程序挂起之前的 30 秒时钟时间执行工作。
后台获取是一种智能的轮询机制,它很适合需要经常更新内容的程序,像社交网络,新闻或天气的程序。
开启后台获取的第一步是在 info plist 文件中对 UIBackgroundModes 键指定特定的值。
UIApplicationBackgroundFetchIntervalMinimum 这个值要求系统尽可能频繁地去管理你的程序到底什么时候应该被唤醒,但如果你不需要这样的话,你也应该指定一个你想要的的时间间隔。
取消后台获取
把 minimumBackgroundFetchInterval 设置为 UIApplicationBackgroundFetchIntervalNever ,这样可以节省资源。
只有 30 秒的时间来确定获取的新内容是否可用,然后处理新内容并更新界面。
完成回调的执行有两个目的。首先,系统会估量你的进程消耗的电量,并根据
其次,当你调用完成的处理代码时,应用的界面缩略图会被采用,并更新应用程序切换器。
可能想知道 iOS 是如何在应用程序后台运行时获得界面截图的,并且想知道应用程序的生命周期与后台获取之间有什么关系。如
在屏幕外渲染的。
对于 view 中的 layer 来说,对动作的搜索只会到第一步为止
属性在动画 block 中改变时,view 将向 layer 返回一个基本的动画,然后动画通过通常的 addAnimation:forKey: 方法被添加到 layer 中,就像显式地添加动画那样。再
view 和 layer 之间的交互很容易被观测到
归功于 UIView 的 +layerClass 类方法,view 和 layer 之间的交互很容易被观测到
重写 layer 子类中的 addAnimation:forKey: 并输出一些东西来验证它是否确实被调用
只有 fromValue 不是 nil 时,在 fromValue 和属性当前显示层的值之间进行插值。
将一个属性改变为新的值,然后将动画对象添加到 layer 上:
真正有意思的是这个 delegate 实现了 animationDidStart: 和 animationDidStop:finished: ,并将信息传给了它自己的 delegate
自定义基于 block 的动画 APIs
@property (copy) void(^start)(void);
@property (copy) void(^stop)(BOOL);
block 声明
类方法初始化两个block成员参数
+(instancetype)animationDelegateWithBeginning:(void(^)(void))beginning
首先要做的是当一个 layer 属性变化时获取 delegate 的回调
SEL 类型参数定义
SEL extendedSelector = @selector(DR_actionForLayer:forKey:);
有意思的是,iOS 添加的一个基于 block 的动画 API 也遇到了同样的问题。使用和上面一样的观察手段,我们就能知道它是如何绕开这个麻烦的。对于每个关键帧,在属性变化时,view 返回 nil ,但是却存储下需要的状态。这样就能在所有关键帧 block 执行后创建一个 CAKeyframeAnimationz 对象
。帧动画需要所有的值都是已知的,而对我们的情况来说,新的值还没有被设定,因此我们也就无从知晓。
Core Animation 将会为这个图层申请一个后备存储,用来保存那些方法绘制进来的位图。那些方法内的代码将会运行在 CPU 上,结果将会被上传到 GPU。
Core Animation 通过 Core Graphics 的一端和 OpenGL ES 的另一端,精心策划基于 CPU 的位图绘制。因为
当你的程序进行位图绘制时,不管使用哪种方式,都是基于 Quartz 2D 的。也就是说,CPU 部分实现的绘制是通过 Quartz 2D 实现的。尽管
当使用 UIKit 或者 AppKit 时,上下文是唯一的
UIkit 维护着一个上下文堆栈,UIKit 方法总是绘制到最顶层的上下文中
可以创建我们自己的上下文,叫做基于位图的上下文,比如 CGBitmapContextCreate() .
UIKit 使用 UIGraphicsBeginImageContextWithOptions() 和 UIGraphicsEndImageContext() 方便的创建类似于 CGBitmapContextCreate() 的位图上下文。
UIKit 使用 UIGraphicsBeginImageContextWithOptions() 和 UIGraphicsEndImageContext() 方便的创建类似于 CGBitmapContextCreate() 的位图上下文。混合调用 UIKit 和 Core Graphics 非常简单:
屏幕上的像素是由红,绿,蓝三种颜色组件构成的。因此,位图数据有时也被叫做 RGB 数据。
有很多种不同的方式在内存中展现RGB位图数据。
颜色组件:红,绿,蓝中得到一个值
从组建中得到一个像素值
颜色组件:红,绿,蓝中得到一个值。而大多数情况下,我们有第四个组件:透明度。最终我们从每个像素中得到四个单独的值。
这个格式经常被叫做 ARGB。每个像素占用 4 字节(32bpp),每一个颜色组件是1字节(8bpc).每个像素有一个 alpha 值,这个值总是最先得到的(在RGB值之前),最终红、绿、蓝的值都会被预先乘以 alpha 的值
像素在内存中的布局形态,每个颜色组建的4字节
8bp像素假设自己的透明组建为100
这常被叫做 xRGB。像素并没有任何 alpha 值(他们都被假定为100%不透明),但是内存布局是一样的
8bpc像素即布局形式为xRGB/假设自己的透明组建为100
现代的 CPU 不喜欢装载(读取)不对齐的数据,特别是当将这种数据和上面没有 alpha 值格式的数据混合时,算法需要做很多挪动和蒙板操作。
8bpc像素即布局形式为xRGB,32bit 内存空间假设自己的透明组建为100
每一个颜色组件都在它自己的内存区域,也就是说它是二维的
每一个颜色组件都在它自己的内存区域,也就是说它是二维的。比如 RGB 数据,我们有三个独立的内存区域,一个大的区域包含了所有像素的红颜色的值,一个包含了所有绿颜色的值,一个包含了所有蓝颜色的值
二维组建/二维数据
对于每一个二维颜色,JPEG 使用一种基于离散余弦变换(简称 DCT 变换)的算法,将空间信息转变到频域.这个信息然后被量子化,排好序,并且用一种哈夫曼编码的变种来压缩。
为什么当你通过 JPEG 文件创建一个 UIImage 并且绘制到屏幕上时,
这也是为什么当你通过 JPEG 文件创建一个 UIImage 并且绘制到屏幕上时,将会有一个延时,因为 CPU 这时候忙于解压这个 JPEG
当你在你的程序中使用图片时,你需要坚持这两种格式: JPEG 或者 PNG。读写这种格式文件的压缩和解压文件能表现出很高的性能,另外,还支持并行操作
xcode 改变png 特有的解压缩算法
Xcode 改变他们,让 iOS 通过一种对正常 PNG 不起作用的算法来对他们解压缩。值得注意的重点是,这改变了像素的布局。
并行绘图的原理
在 Core Graphics 下,所有 Core Graphics 绘制方法都需要一个上下文参数来指定绘制到那个上下文中。UIKit 有一个当前上下文的概念(也就是绘制到哪儿去)。这个当前的上下文就是 per-thread.
并行绘图的原理 参看离屏渲染原理加以理解
,你在这个方法中所做的所有绘图的代码都是线程安全的,也就是说,当你访问属性等等,他们需要线程安全。因为你是在另一个队列中调用这个方法的。
创建一个单独的渲染类,并设置所有需要的属性,然后通过触发来渲染图片
创建一个单独的渲染类,并设置所有需要的属性,然后通过触发来渲染图片。如果这样,你可以通过使用简单的 UIImageView 或者 UITableViewCell
这样做会告诉 Core Animation 使用图片的位图数据作为纹理。
通常,当你使用 CALayer 时,你会设置它的内容为一个图片。这到底做了什么?这样做会告诉 Core Animation 使用图片的位图数据作为纹理。
图层有一个后备存储,这便是被用来绘制到屏幕上的位图。
你设置 drawsAsynchronously 为 YES 时,发生了什么?你的 -drawRect:/-drawInContext: 方法仍然会被在主线程上调用。但是所有调用 Core Graphics 的操作都不会被执行。取而代之的是,绘制命令被推迟,并且在后台线程中异步执行
你可以设置自己队列的目标队列。以这种方式,你可以将不同队列链接在一起
多线程编程中,最常见的情形是你有一个资源,每次只有一个线程被允许访问这个资源
GCD 有可以让多线程运行的并发队列。
使用 barrier 来分发这个 block。这样的一个 block 的运行时机是,在它之前所有计划好的 block 完成之后,并且在所有它后面的 block 运行之前。
这种行为是和时机有关系的,所以很可能在开发阶段没有崩溃,但是你的用户使用时却不断 crash。
。UIKit Main Thread Guard 是一段用来监视每一次对 setNeedsLayout 和 setNeedsDisplay 的调用代码,并检查它们是否是在主线程被调用
当你从一个网络套接字中读取数据时,你要么做一个阻塞的读操作,也就是让你个线程一直等待直到数据变得可用,或者是做反复的轮询。这两种方法都是很浪费资源并且无法度量。
C 语言 API 存在的另一个问题就是,缓冲区没有所有权的概念
C 语言 API 存在的另一个问题就是,缓冲区没有所有权的概念,所以函数不得不将数据再次拷贝到自己的缓冲区中——又一次的拷贝
必须记住 GCD 只是纯 C 的 API,并且不能使用Objective-C
建一个缓冲区,这个缓冲区要么是基于栈的,要么是 malloc 操作分配的内存区域 —— 这些都没有所有权
dispatch_data_t 的一个相当独特的属性是它可以基于零碎的内存区域。这解决了我们刚提到的组合内存的问题。当你要将两个数据对象连接起来时:
以使用 dispatch_data_apply 来遍历对象 c 持有的内存区域:
有两种从根本上不同类型的通道:流和随机存取。
是串行的,也就是说,任何给定的时间内,只能有一个单独的 block 运行。这就是隔离队列(原文:isolation queues。译注)的运行方式。队
当使用 GCD 来完成并发的工作时,你不必考虑线程方面的问题,取而代之的,只需考虑队列和功能点(提交给队列的 block
所有的并发编程 API 都是构建于线程之上的 —— 包括 GCD 和操作队列(operation queues)
线程(thread)是组成进程的子单元,操作系统的
无法控制你的代码在什么地方以及什么时候被调度,以及无法控制执行多长时间后将被暂停,以便轮换执行别的任务
NSThread 是 Objective-C 对 pthread 的一个封装
开发者可以利用 NSThread 的一个子类来定义一个线程,在这个子类的中封装需要在后台线程运行的代码。针对上
通过检测到线程的 isFinished 属性来检测新生成的线程是否已经结束,并获取结
通过 GCD,开发者不用再直接跟线程打交道了,只需要向队列中添加代码块即可,GCD 在后端管理着一个线程池。GCD 不仅决定着你的代码块将在哪个线程被执行,它还根据可用的系统资源对这些线程进行管理
虽然 GCD 是一个低层级的 C API ,但是它使用起来非常的直接。不过这也容易使开发者忘记并发编程中的许多注意事项和陷阱。读者可以阅读本文后面的并发编程中面临的挑战,
操作队列则在 GCD 之上实现了一些方便的功能,这些功能对于 app 的开发者来说通常是最好最安全的选择
NSOperationQueue 有两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行
以通过重写 main 或者 start 方法 来定义自己的 operations
为了能使用操作队列所提供的取消功能,你需要在长时间操作中时不时地检查 isCancelled 属性:
这种情况下,你必须手动管理操作的状态。
为了让操作队列能够捕获到操作的改变,需要将状态的属性以配合 KVO 的方式进行实现
定义好 operation 类之后,就可以很容易的将一个 operation 添加到队列中:
另外,你也可以将 block 添加到操作队列中。这有时候会非常的方便,比如你希望在主队列中调度一个一次性任务:
除了提供基本的调度操作或 block 外,操作队列还提供了在 GCD 中不太容易处理好的特性的功能。例如,你可以通过 maxConcurrentOperationCount 属性来控制一个特定队列中可以有多少个操作参与并发执行
一个 run loop 总是绑定到某个特定的线程中
run loop 可以运行在不同的模式中,每种模式都定义了一组事件,供 run loop 做出响应。这在对应 main run loop 中暂时性的将某个任务优先执行
其他线程默认情况下都没有设置 run loop。你也可以自行为其他线程设置 run loop ,
在数码摄影的时代之前,摄影通常是记录在纸上或者胶卷上的。而今天,摄影将光转换为比特信息。
。曝光也指单位面积上光的数量
一张照片过度地过曝或者欠曝的话,是没有办法进行修复
在欠曝图像中,即使我们尝试将它调亮,图像的暗部依然被 “卡” 在黑色,我们没有办法让图像中的笔确实拥有不同的颜色。过曝图像中也有大部分区域被卡在白色或者灰色:编织带和硬币细节已经完全丢失
在摄影中,通过调整这三者中任意一个来让进光量翻倍或者减半,就叫做改变了 “一档” 曝光。每一档曝光对于这三者 (快门速度,光圈和曝光度) 来说对应的是不同的数字变化。但如果我们将快门速度调整一档,我们需要通过调整 ISO 或者光圈一档来进行补偿,才能获取同样的进光量
当我们捕捉图片时,图像传感器捕捉一段时间的光线。这段时间被叫做快门速度,因为它表示了快门打开和关闭的快慢
如果我们在拍照时相机和场景是完全静止的,我们可以使用任意长的曝光时间 (快门速度),但是更多情况并非如此。我们身边的东西都一直在移动,特别是对于 iPhone 来说,相机本身也一直在移动。
当物体移动的足够快的时候,它在整个曝光时间里便不会只停留在某一个点,这会导致图片模糊。一般来说我们希望画面清晰、不模糊,因此我们通常希望快门速度在 1/100 秒或者更快/更短。对于高速运动的物体的摄影,我们可能还需要选择一个更快的快门速度。
ISO 值也被称作胶卷速度。在数码摄影中,它被用来衡量图像传感器对光的灵敏程度,以及因此带来的曝光噪音。
每次我们将 ISO 加倍 (比如从 ISO 100 到 ISO 200),我们就只需要一半的进光量
圈是用来衡量到达图像感应器的光所通过的通孔的大小
圈是用来衡量到达图像感应器的光所通过的通孔的大小的。光圈值以 F 比例 (焦比) 来进行标定,比如 ƒ/5.6,其中 5.6 表示镜头焦距与光圈 (也就是通孔) 的有效直径的比例。
除了影响进光量,光圈还会对景深造成影响。这和对焦有关系。相机中的光学系统会将与相机一定距离范围内的物体渲染清晰。当我们改变光圈时,这个距离范围将变宽或者变窄。
相机只能将离相机一定距离范围内的物体渲染清晰。在这个范围内的物体就是被聚焦的,而如果太近或者太远而导致模糊的,它们就是失焦的。
包括 iPhone 在内的大多数的相机都有自动对焦 (AF),相机会猜测图片的哪个部分需要被聚焦,并依此来调节焦距
机镜头是由一系列光学组件构成的,它们负责引导和聚集光束。对相机焦距的调整,其实是通过物理移动这些镜头组件来完成的
模块化相机 - 比如 单反 - 可以让你在不同的镜头间切换。即
镜头系统最重要的指标是它的焦距 - 主要是它的放大倍率和视野角
在你的 iPhone 相机里面,有一个图像传感器。这个部分就相当于我们眼睛里的视网膜。图像传感器可以将光或者光子转换为电信号
应用通常是一些用户用例的集合。用例也被称为验收标准,或行为集,它们用来描述应用的用途。
由于交互器是一个 PONSO (Plain Old NSObject ,普通的 NSObject ),它主要包含了逻辑,因此很容易使用 TDD 进行开发
KVC 还有更多可以谈的。集合( NSArray , NSSet 等)结合 KVC 可以拥有一些强大的集合操作。还有,对象可以支持用 KVC 通过代理对象访问非常规的属性
我们也要指出有些集合是不能被观察的。KVO 旨在观察关系 (relationship) 而不是集合。我们不能观察 NSArray ,我们只能观察一个对象的属性
让我们在一次强调一下:KVC 不会做任何的验证,也不会调用任何 KVV 的方法。那是你的控制器需要做的事情。通过 KVV 实现你自己的验证方法会保证它们的一致性
KVO 能帮助我们让视图和模型保持同步。控制器可以观察视图依赖的属性变化
Foundation 框架提供的表示属性依赖的机制如下:
如果我们写了 -setLComponent: 或者我们选择使用自动 synthesize 的 lComponent 的 accessor 到时候就会发生这样的事情。
有些情况下当我们需要 override -setLComponent: 并且我们要控制是否发送键值改变的通知的时候,我们要做以下的事
我们常常需要当一个值改变的时候更新 UI,但是我们也要在第一次运行代码的时候更新一次 UI。我们可以用 KVO 并添加 NSKeyValueObservingOptionInitial 的选项 来一箭双雕地做好这样的事情。这将会让 KVO 通知在调用 -addObserver:forKeyPath:... 到时候也被触发。
如果一个被调用的方法需要发送一个一次性的消息作为回复,那么使用 block 是很好的选择
如果一个被调用的方法需要发送一个一次性的消息作为回复,那么使用 block 是很好的选择
通过 GCD,开发者不用再直接跟线程打交道了,只需要向队列中添加代码块即可,GCD 在后端管理着一个线程池。GCD 不仅决定着你的代码块将在哪个线程被执行,它还根据可用的系统资源对这些线程进行管理
GCD 带来的另一个重要改变是,作为开发者可以将工作考虑为一个队列,而不是一堆线程,这种并行的抽象模型更容易掌握和使用
享的资源,那么在不同优先级的队列中调度这些任务
NSOperationQueue 有两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行
这种情况下,你必须手动管理操作的状态。
为了让操作队列能够捕获到操作的改变,需要将状态的属性以配合 KVO 的方式进行实现
们默认的 setter 来进行设置的话,你就需要在合适的
重写 operation 的 description 方
在别的线程中添加一个 run loop ,那么不要忘记在 run loop 中至少添加一个 input source 。
动画不能通过使用标准 Layer 属性动画来实
如果我们写了 -setLComponent: 或者我们选择使用自动 synthesize 的 lComponent 的 accessor 到时候就会发生这样的事情。
有些情况下当我们需要 override -setLComponent: 并且我们要控制是否发送键值改变的通知的时候,我们要做以下的事